package com.planw.beetl.sql;

import com.intellij.codeInsight.completion.CompletionContributor;
import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.codeInsight.completion.CompletionProvider;
import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.patterns.PatternCondition;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.patterns.PsiElementPattern.Capture;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiLanguageInjectionHost;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiReferenceParameterList;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypeElement;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageManagerImpl;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.ui.JBColor;
import com.intellij.util.PlatformIcons;
import com.intellij.util.ProcessingContext;
import com.planw.beetl.model.TreeNode;
import com.planw.beetl.service.BeetlSqlService;
import com.planw.beetl.utils.PsiKt;
import com.planw.beetl.utils.StrUtil;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.intellij.plugins.markdown.lang.MarkdownElementTypes;
import org.intellij.plugins.markdown.lang.MarkdownLanguage;
import org.intellij.plugins.markdown.lang.MarkdownTokenTypeSets;
import org.intellij.plugins.markdown.lang.MarkdownTokenTypes;
import org.intellij.plugins.markdown.lang.psi.impl.MarkdownCodeBlockImpl;
import org.intellij.plugins.markdown.lang.psi.impl.MarkdownCodeFenceImpl;
import org.intellij.plugins.markdown.lang.psi.impl.MarkdownParagraphImpl;
import org.jetbrains.annotations.NotNull;

/*
 * 关于idea的classloader的几点：
 * 1. 当前线程的classloader加载路径是运行时的idea下的lib目录
 * 2. GroupTemplate.class.getClassLoader() 这种方式获取的classloader是idea为每一个plugin独立的classloader。也只有这种方式，才能加载到我们引入的第三方jar
 * 3. ClassLoader.getSystemClassLoader() 获取的classloader也是运行时idea环境的lib目录
 * 4. 插件的class由pluginclassloader加载，必须在plugin.xml中配置depends，依赖插件的id
 * */
public class BeetlSqlCompletionContributor extends CompletionContributor {

  private static final Logger logger = Logger.getInstance(BeetlSqlCompletionContributor.class);

  public BeetlSqlCompletionContributor() {

    Capture<PsiElement> pattern = PlatformPatterns.psiElement()
        .with(new MarkdownBeetlSqlPatternCondition());
    extend(null, pattern,
        new BeetlSqlCompletionProvider()
    );
  }

  static class MarkdownBeetlSqlPatternCondition extends PatternCondition<PsiElement> {

    private static final Logger logger = Logger.getInstance(MarkdownBeetlSqlPatternCondition.class);

    public MarkdownBeetlSqlPatternCondition() {

      super(" BeetlSqlPatternCondition ");
    }

    @Override
    public boolean accepts(@NotNull PsiElement psiElement, ProcessingContext processingContext) {

      logger.info(" 【beetlsql】 accepts starting... ");
      Project project = psiElement.getProject();
      BeetlSqlService beetlSqlService = BeetlSqlService.getInstance(project);
//      无beetlsql依赖，就跳过代码完成
      if (!beetlSqlService.isDependOnBeetlSql()) {
        logger.info(" 【beetlsql】项目无beetlsql依赖库 ");
        return false;
      }
      PsiFile psiFile = psiElement.getContainingFile();
      InjectedLanguageManagerImpl ilm = InjectedLanguageManagerImpl
          .getInstanceImpl(project);
      boolean injectedFragment = ilm.isInjectedFragment(psiFile);
      logger.info("【beetlsql】 injectedFragment is " + injectedFragment);
      Language fileLanguage;
      PsiElement parent;
      if (injectedFragment) {
        PsiFile topLevelFile = ilm.getTopLevelFile(psiElement);
        fileLanguage = topLevelFile.getLanguage();
        PsiLanguageInjectionHost injectionHost = ilm.getInjectionHost(psiElement);
        PsiElement originalElement = injectionHost.getOriginalElement();
        parent = originalElement;
      } else {
        fileLanguage = PsiUtilCore.findLanguageFromElement(psiElement);
        parent = psiElement.getParent();
      }

      logger.info(" 【beetlsql】 accepts ended  ");
// 必须同时满足是markdown和下面三个语法元素中才提示
//      这几个markdown元素是可能存在beetlsql语法的位置
      return fileLanguage.is(MarkdownLanguage.INSTANCE) && (parent instanceof MarkdownParagraphImpl
          || parent instanceof MarkdownCodeFenceImpl
          || parent instanceof MarkdownCodeBlockImpl);
    }

  }

  static class BeetlSqlCompletionProvider extends CompletionProvider<CompletionParameters> {

    private static final Logger logger = Logger.getInstance(BeetlSqlCompletionProvider.class);

    BeetlSqlService beetlSqlService;

    // 用于搜索代码完成的文本 例如： sex user.name 这一行，我们只需要最后的user.name
    private static final Pattern autocompletion = Pattern.compile("[\\w\\d\\.]+");

    @Override
    protected void addCompletions(@NotNull CompletionParameters parameters,
        @NotNull ProcessingContext context, @NotNull CompletionResultSet result) {

      logger.info(" 【beetlsql】 addCompletions starting... ");
      PsiFile currentPsiFile = parameters.getOriginalFile();
      Project project = currentPsiFile.getProject();
      PsiElement positionPsi = parameters.getPosition();
      beetlSqlService = BeetlSqlService.getInstance(project);
      InjectedLanguageManagerImpl ilm = InjectedLanguageManagerImpl
          .getInstanceImpl(project);
      boolean injectedFragment = ilm.isInjectedFragment(currentPsiFile);
      logger.info(" 【beetlsql】 ilm isInjectedFragment  " + injectedFragment);

//      sqlid 既是mapper的方法名
      PsiElement curNode;
      if (injectedFragment) {
        curNode = ilm.getInjectionHost(positionPsi);
      } else {
        curNode = positionPsi;
      }
      String sqlId = findSqlId(curNode);
      logger.info(" 【beetlsql】find sql id  " + sqlId);
      if (StringUtils.isBlank(sqlId)) {
        return;
      }
//      获取对应的mapper类
      PsiFile mdPsiFile = ilm.getTopLevelFile(positionPsi);
      PsiClass mapper = beetlSqlService.getMapperByMdFile(mdPsiFile);
      logger.info(String.format(" 【beetlsql】通过【%s】sqlid找到mapper类【%s】 ", sqlId, mapper));
      if (mapper != null) {
//      查找父类方法
        PsiMethod[] methods = mapper.findMethodsByName(sqlId, true);
        if (ArrayUtils.isEmpty(methods)) {
          logger.warn(String.format(" 【beetlsql】没有找到匹配【%s】sqlid的mapper方法 ", sqlId));
          return;
        }
        PsiElement cursorElement = parameters.getOriginalPosition();
        String word = Optional.ofNullable(cursorElement).map(PsiElement::getText)
            .orElse(StringUtils.EMPTY);
        logger.info("【beetlsql】word before " + word);

        Matcher matcher = autocompletion.matcher(word);
        String t = StringUtils.EMPTY;
        while (matcher.find()) {
//          查找sex  user.name 文本的user.name 部分
          t = matcher.group();
        }
        word = t;
//        改变前缀匹配
        result = result.withPrefixMatcher(word);
        logger.info("【beetlsql】word after" + word);
//        如果是点号结尾，给一个星号，说明查询点号后面的所有可能代码
        if (StringUtils.endsWith(word, ".")) {
          word = word + "*";
        }
        String[] searchStrs = word.split("\\.");
        if (ArrayUtils.isNotEmpty(searchStrs)) {
          logger.info(" 【beetlsql】进入匹配过程之前 ");
          addResultSet(result, methods, searchStrs);
          logger.info(" 【beetlsql】进入匹配过程之后 ");
          List<LookupElement> beetlSqlFunctions = beetlSqlService
              .findBeetlSqlFunction(searchStrs[0]);
          for (LookupElement element : beetlSqlFunctions) {
            logger.info("【beetlsql】CompletionResultSet " + element.getLookupString());
            result.addElement(element);
          }
        }
      }
    }

    /**
     * 找到sqlid那行markdown语法（既是SETEXT_1语法元素）
     *
     * @param curNode
     *     光标所在的psielement
     */
    private String findSqlId(PsiElement curNode) {

      logger.info("【beetlsql】 findSqlId before curNode" + curNode);
      // MarkdownTokenTypes.SETEXT_1 的上一级就是 psifile ，所以应该找到当前光标所处 psifile 下的第一级
      while (ObjectUtils
          .notEqual(Optional.ofNullable(curNode).map(PsiElement::getParent).map(PsiElement::getNode)
              .map(ASTNode::getElementType).orElse(null), MarkdownElementTypes.MARKDOWN_FILE)) {
        curNode = curNode.getParent();
      }
      logger.info("【beetlsql】 findSqlId after curNode" + curNode);
//      查找最近的Sql Id。既：markdown 语法中的 段落标题 === 语法
      PsiElement backwordSetext = PsiTreeUtil
          .findSiblingBackward(curNode, MarkdownTokenTypeSets.SETEXT_1, null);

      ASTNode sqlIdNode = backwordSetext.getNode()
          .findChildByType(MarkdownTokenTypes.SETEXT_CONTENT)
          .findChildByType(MarkdownTokenTypes.TEXT);

      return sqlIdNode.getText();
    }

    private void addResultSet(CompletionResultSet result, PsiMethod[] methods,
        String[] searchStrs) {

      TreeNode root = new TreeNode();
      for (PsiMethod method : methods) {
        PsiParameter[] psiParameters = method.getParameterList().getParameters();
        int parameterSize = psiParameters.length;
        for (PsiParameter parameter : psiParameters) {
//          beetlsql3 的分页不具备传参
          if (beetlSqlService.isPageRequest(parameter)) {
            continue;
          }
          //          每个参数都应该完整的搜索匹配输入代码
          Queue<String> searchStrsQueue = new ArrayBlockingQueue<String>(searchStrs.length, false,
              Arrays.asList(searchStrs));
          TreeNode child = new TreeNode();
          root.getChildren().add(child);
          //          处理基本类型
          if (searchStrsQueue.size() == 1 && PsiKt.isPrimitive(parameter.getType())) {
            String search = searchStrsQueue.poll();
            addPrimitiveParameter(child, parameter, search);
            continue;
          }
//          判断参数的名称是否显示
          boolean rootParameter = isRootParameter(parameter, parameterSize);
          if (!rootParameter) {
            String tempSearch = searchStrsQueue.peek();
            String path = beetlSqlService.getParamAnnotationValue(parameter);
            if (StringUtils.isBlank(path)) {
              path = parameter.getName();
            }
            if (StrUtil.searchCharSequence(path, tempSearch)) {
              child.setType(parameter.getType());
              child.setPath(path);
              child.setPsiElement(parameter.getOriginalElement());
              child.setShow(true);
              searchStrsQueue.poll();
            }
          }

//          处理非基本类型参数
          dsfParameterTypeFields(child, parameter, searchStrsQueue);
        }
      }
      handleTreeNodeToLookupElement(result, root);
    }

    /**
     * <pre>
     * entity类参数是否从参数级开始书写？
     * 以参数级开始书写包括以下情况：
     * 1、参数个数大于1的entity类
     * 2、基本类型
     * 3、@Param注解值
     * 不以参数级开始包括：
     * 1、参数个数为1的entity类
     * 2、被Root注解
     * </pre>
     */
    private boolean isRootParameter(PsiParameter parameter, int parameterSize) {

      if (beetlSqlService.getParamAnnotation(parameter) != null) {
        return false;
      }
      if (parameterSize == 1) {
        return true;
      }
      PsiAnnotation root = parameter.getAnnotation("org.beetl.sql.mapper.annotation.Root");
      return root != null;
    }

    private void addPrimitiveParameter(TreeNode child, PsiParameter parameter,
        String search) {

//      优先 org.beetl.sql.core.annotatoin.Param 注解值作为代码提示值，注解只有一级，因为只对基本类型和string注解
      String text = beetlSqlService.getParamAnnotationValue(parameter);
      if (StringUtils.isBlank(text)) {
//        无param注解
        text = parameter.getName();
      }
      if (StrUtil.searchCharSequence(text, search)) {
        child.setType(parameter.getType());
        child.setPath(parameter.getName());
        child.setPsiElement(parameter.getOriginalElement());
        child.setShow(true);
      }
    }

    /**
     * 深度搜索字段，截止到；并且将搜索到的按照点号拼接
     */
    private void dsfParameterTypeFields(TreeNode child, PsiParameter parameter,
        Queue<String> searchStrQueue) {

//        判断PageQuery，并判断是否有泛型，没有直接跳过
      String parameterClassName = Optional
          .ofNullable(PsiUtil.resolveClassInType(parameter.getType()))
          .map(PsiClass::getQualifiedName).orElse(StringUtils.EMPTY);
      PsiClass entityPsiClass = null;
      if (StringUtils.equals(BeetlSqlService.PAGE_QUERY_CLASS, parameterClassName)) {
//        获取泛型参数
        PsiTypeElement[] typeParameterElements = Optional.ofNullable(parameter.getTypeElement())
            .map(PsiTypeElement::getInnermostComponentReferenceElement)
            .map(PsiJavaCodeReferenceElement::getParameterList)
            .map(PsiReferenceParameterList::getTypeParameterElements).orElse(new PsiTypeElement[0]);
        PsiType entityType = Arrays.stream(typeParameterElements).findFirst()
            .map(PsiTypeElement::getType)
            .orElse(null);
//        解析成psiclass
        entityPsiClass = PsiUtil.resolveClassInType(entityType);
      } else {
        entityPsiClass = PsiUtil.resolveClassInType(parameter.getType());
      }

      //如果是root，则直接使用其属性作为提示
      PsiField[] fields = Optional.ofNullable(entityPsiClass).map(PsiClass::getFields)
          .orElse(new PsiField[0]);
      if (ArrayUtils.isEmpty(fields)) {
        return;
      }
      resolvePsiClass(child, Arrays.asList(fields), searchStrQueue);
    }

    /**
     * 深度遍历实体类字段。比如user.name  user搜索当前实体类字段，name搜索字段可能的实体类的字段，也就是字段的字段
     *
     * @param topNode
     *     用于保存查询到的可能psifield
     * @param searchQueue
     *     以点号分割的用户输入的文本，例如 user.name 变成 user name 两个。
     */
    private void resolvePsiClass(TreeNode topNode, List<PsiField> fields,
        Queue<String> searchQueue) {

      String searchStr = searchQueue.poll();
      if (StringUtils.isBlank(searchStr)) {
        return;
      }
      for (PsiField field : fields) {
        if (StrUtil.searchCharSequence(field.getName(), searchStr)) {
          TreeNode node = new TreeNode();
          node.setPsiElement(field.getOriginalElement());
          node.setPath(field.getName());
          node.setShow(true);
          node.setType(field.getType());
          topNode.addChild(node);
          PsiType fieldType = field.getType();
          if (!PsiKt.isPrimitive(fieldType)) {
            PsiClass fieldPsiCls = PsiUtil.resolveClassInType(fieldType);
            PsiField[] fieldPsiClsFields = Optional.ofNullable(fieldPsiCls).map(PsiClass::getFields)
                .orElse(new PsiField[0]);
            resolvePsiClass(node, Arrays.asList(fieldPsiClsFields), searchQueue);
          }
        }
      }
    }

    /**
     * 深度遍历treenode，每个叶节点的路径为一个lookupelement
     */
    private void handleTreeNodeToLookupElement(CompletionResultSet result,
        TreeNode treeNode) {

      dsfTreeNode(result, treeNode.getChildren(), "");
    }

    private void dsfTreeNode(CompletionResultSet result, List<TreeNode> children,
        String lookupStr) {

      for (TreeNode child : children) {
        String tempLookupStr = lookupStr + child.getPath() + ".";
        List<TreeNode> childChildren = child.getChildren();
        if (!childChildren.isEmpty()) {
          dsfTreeNode(result, childChildren, tempLookupStr);
        } else if (child.isShow()) {
          if (tempLookupStr.startsWith(".")) {
            tempLookupStr = tempLookupStr.substring(1);
          }
          if (tempLookupStr.endsWith(".")) {
            tempLookupStr = tempLookupStr.substring(0, tempLookupStr.length() - 1);
          }
          LookupElement lookupElement = LookupElementBuilder
              .createWithSmartPointer(tempLookupStr, child.getPsiElement())
              .withPresentableText(tempLookupStr)
              .withCaseSensitivity(false)
              .withIcon(PlatformIcons.VARIABLE_ICON)
              .withItemTextForeground(JBColor.BLACK)
              .bold()
              .withTypeText(child.getType().getCanonicalText(), PlatformIcons.VARIABLE_ICON,
                  true)
              .withTypeIconRightAligned(true)
              .withAutoCompletionPolicy(AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE);
          logger.info("【beetlsql】CompletionResultSet " + lookupElement.getLookupString());
          result.addElement(lookupElement);
        }
      }
    }

  }

}